/**
 * @file
 *
 * Modulo per la gestione della griglia principale.
 *
 * La griglia principale viene gestita tramite la variabile ::grid di tipo
 * ::grid_s. Questa struttura contiene tutte le singole celle ed i relativi
 * status ed è il fulcro del programma.
 */

// Author: Adrian Sassatelli
// Copyright (c) 2014 Adrian Sassatelli


#include <iostream>
#include <stdlib.h>
#include <gtk/gtk.h>
#include "grid_data.h"
#include "common.h"
#include "xpm_images.h"
#include "debug.h"

using namespace std;


/* -------------------------------------------------------------------------- */
/*    DICHIARAZIONI                                                           */
/* -------------------------------------------------------------------------- */

grid_s grid;    /**< Struttura dati principale contenente tutte le singole celle
                 * ed i relativi status (definita in grid_data.cc) */


/* -------------------------------------------------------------------------- */
/*    BUTTON                                                                  */
/* -------------------------------------------------------------------------- */

/* Cambia lo stato della singola casella */
void button_change_status (const int acol, const int arow, const button_status_e astatus) {
    DBG_FUNCNAME ("button_change_status", "cambia lo stato della singola casella");

    DBG_GENERIC ("cambio stato per " << acol << "x" << arow);

    pix_image_e v = PI_NONE;

    // ASSERT per debug
    assert ((grid.cols > 0) && (acol >= 0) && (acol <= grid.cols));    // Controllo colonne
    assert ((grid.rows > 0) && (arow >= 0) && (arow <= grid.rows));    // Controllo righe

    // Controllo status precedente per aggiornamento numero mine selezionate
    DBG_BLOCDESC ("controllo stato precedente");
    if ((grid.buttons[arow * grid.cols + acol].status == BS_CHECKED) && (astatus != BS_CHECKED)) {
        grid.selmines--;
    }
    if ((grid.buttons[arow * grid.cols + acol].status != BS_CHECKED) && (astatus == BS_CHECKED)) {
        grid.selmines++;
    }

    // Cambio stato
    DBG_BLOCDESC ("cambio stato");
    grid.buttons[arow * grid.cols + acol].status = astatus;
    switch (astatus) {
        case BS_CHECKED: v = PI_BUTTON_CHECKED; break;
        case BS_MAYBE: v = PI_BUTTON_MAYBE; break;
        case BS_NORMAL: v = PI_BUTTON_NORMAL; break;
        case BS_PRESSED:
            switch (grid.buttons[arow * grid.cols + acol].content) {
                case BC_NEAR0: v = PI_BUTTON_PRESSED; break;
                case BC_NEAR1: v = PI_BUTTON_NEAR1; break;
                case BC_NEAR2: v = PI_BUTTON_NEAR2; break;
                case BC_NEAR3: v = PI_BUTTON_NEAR3; break;
                case BC_NEAR4: v = PI_BUTTON_NEAR4; break;
                case BC_NEAR5: v = PI_BUTTON_NEAR5; break;
                case BC_NEAR6: v = PI_BUTTON_NEAR6; break;
                case BC_NEAR7: v = PI_BUTTON_NEAR7; break;
                case BC_NEAR8: v = PI_BUTTON_NEAR8; break;
                case BC_MINE:    // Trovata mina, fermo la partita
                    DBG_GENERIC ("trovata mina. fine partita")

                    grid_lost_reveal ();    // Visualizza posizione mine

                    v = PI_BUTTON_EXPLODED;
                    break;
                default: assert (false);    // Non possibile
            }
            break;
        default: assert (false);    // Non possibile
    }

    image_set_from_pix (grid.buttons[arow * grid.cols + acol].image, v);
}

/* -------------------------------------------------------------------------- */

/* Cambia stato alle caselle adiacenti a quella data */
void button_reveal (const int acol, const int arow) {
    DBG_FUNCNAME ("button_reveal", "cambio stato alle caselle circostanti a " << acol << "x" << arow);

    // Analizza il gruppo di caselle adiacenti
    DBG_BLOCDESC ("analizza il gruppo di caselle adiacenti");
    for (int rn = (arow == 0 ? 0 : arow - 1); rn <= (arow == grid.rows - 1 ? grid.rows - 1 : arow + 1); rn++) {
        for (int cn = (acol == 0 ? 0 : acol - 1); cn <= (acol == grid.cols - 1 ? grid.cols - 1 : acol + 1); cn++) {
            // Analizza solo quelle che ancora non sono state cliccate e che non contengono una mina
            if ((grid.buttons[rn * grid.cols + cn].content != BC_MINE) && (grid.buttons[rn * grid.cols + cn].status == BS_NORMAL)) {
                DBG_GENERIC ("trovata casella da mostrare in " << cn << "x" << rn);
                button_change_status (cn, rn, BS_PRESSED);

                if (grid.buttons[rn * grid.cols + cn].content == BC_NEAR0) {
                    DBG_GENERIC ("  trovata casella da analizzare ricorsivamente in " << cn << "x" << rn);
                    button_reveal (cn, rn);    // Analizza le altre celle adiacenti
                }
            }
        }
    }
}

/* -------------------------------------------------------------------------- */

/* Esegui procedure per tasto sinistro su casella */
void button_left_click (const int acol, const int arow) {
    DBG_FUNCNAME ("button_left_click", "rivela contenuto della casella");

    if (grid.game_status != GS_NORMAL) return;    // Esci se la partita è già finita

    if ((grid.buttons[arow * grid.cols + acol].status == BS_NORMAL) || (grid.buttons[arow * grid.cols + acol].status == BS_MAYBE)) {
        button_change_status (acol, arow, BS_PRESSED);

        if (grid.buttons[arow * grid.cols + acol].content != BC_MINE) {    // Analizza caselle adiacenti
            button_reveal (acol, arow);
        }
    }
}

/* -------------------------------------------------------------------------- */

/* Esegui procedure per tasto destro su casella */
void button_right_click (const int acol, const int arow) {
    DBG_FUNCNAME ("button_right_click", "cambia stato alla casella");

    if (grid.game_status != GS_NORMAL) return;    // Esci se la partita è già finita

    DBG_GENERIC ("tasto destro su " << acol << "x" << arow);

    switch (grid.buttons[arow * grid.cols + acol].status) {
        case BS_NORMAL: button_change_status (acol, arow, BS_CHECKED); break;
        case BS_CHECKED: button_change_status (acol, arow, BS_MAYBE); break;
        case BS_MAYBE: button_change_status (acol, arow, BS_NORMAL); break;
        default: ;
    }
}


/* -------------------------------------------------------------------------- */
/*    GRID                                                                    */
/* -------------------------------------------------------------------------- */

/* Controlla se la partita è stata vinta */
gboolean grid_check_win () {
    DBG_FUNCNAME ("grid_check_win", "controlla se la partita è stata vinta");

    // Controllo se il numero di caselle segnate = numero di mine
    if (grid.nummines == grid.selmines) {
        // Controllo che tutte le caselle segnate contengano una mina
        for (int r = 0; r < grid.rows; r++) {
            for (int c = 0; c < grid.cols; c++) {
                if ((grid.buttons[r * grid.cols + c].status == BS_CHECKED) && (grid.buttons[r * grid.cols + c].content != BC_MINE)) {
                    DBG_GENERIC ("trovata una casella segnata senza mina in " << c << "x" << r);
                    return false;    // Ho trovato una casella segnata senza mina
                }
            }
        }

        DBG_GENERIC ("WIN! tutte le caselle segnate contengono una mina");
        grid_win_reveal ();    // Visualizza posizione mine

        return true;
    }

    // Controllo se il numero di caselle rimaste = numero mine
    int i = 0;
    for (int r = 0; r < grid.rows; r++) {
        for (int c = 0; c < grid.cols; c++) {
            if (grid.buttons[r * grid.cols + c].status != BS_PRESSED) i++;
        }
    }

    if (i == grid.nummines) {
        DBG_GENERIC ("WIN! rimaste solo caselle con mine");
        grid_win_reveal ();    // Visualizza posizione mine

        return true;
    } else {
        DBG_GENERIC ("il numero di caselle rimaste diverso dal numero di mine");
        return false;
    }
}

/* -------------------------------------------------------------------------- */

/* Cancella la griglia principale */
void grid_delete () {
    DBG_FUNCNAME ("grid_delete", "cancella la griglia principale")

    if ((grid.rows == 0) && (grid.cols == 0)) {
        DBG_GENERIC ("griglia vuota");
        return;    // Controlla che la griglia non sia già vuota
    }

    // ASSERT controlli per debug
    assert ((grid.rows != 0) && (grid.cols != 0));    // Controllo coerenza dimensioni

    // Distrugge l'array delle caselle
    DBG_BLOCDESC ("distruzione array caselle");
    for (int r = 0; r < grid.rows; r++) {
        for (int c = 0; c < grid.cols; c++) {
//            gtk_widget_destroy ((GtkWidget *) grid.buttons[r * grid.cols + c].image);     // Non serve perchè il destroy del container distrugge anche il contenuto
            gtk_widget_destroy ((GtkWidget *) grid.buttons[r * grid.cols + c].container);
        }
    }
    delete [] grid.buttons;

    return;
}

/* -------------------------------------------------------------------------- */

/* Popola la griglia principale */
gboolean grid_fill (const int acols, const int arows, const int anummines, GtkWidget *agrid, GCallback ahandler) {
    DBG_FUNCNAME ("grid_fill", "popola la griglia principale");

    DBG_GENERIC ("creazione griglia " << acols << "x" << arows << " " << anummines);

    // ASSERT per debug
    assert (acols > 0);    // Controllo colonne
    assert (arows > 0);    // Controllo righe
    assert (anummines < arows * acols);    // Controllo numero mine

    // Controllo se le dimensioni della griglia sono le stesse di quella precedente
    if ((grid.rows != arows) || (grid.cols != acols)) {
        // Ricreo la griglia
        DBG_BLOCDESC ("dimensioni della griglia diverse da quella precedente. ricreo");

        DBG_GENERIC ("cancello la griglia");
        grid_delete ();    // Cancello la griglia

        // Crea array nuovi pulsanti e imposta valori iniziali
        DBG_GENERIC ("creo array nuovi pulsanti");
        grid.buttons = new button_s[arows * acols];
        for (int r = 0; r < arows; r++) {
            for (int c = 0; c < acols; c++) {
				// Creazione componenti
				grid.buttons[r * acols + c].image = (GtkImage *) gtk_image_new ();
                grid.buttons[r * acols + c].container = gtk_event_box_new ();
                gtk_container_add ((GtkContainer *) grid.buttons[r * acols + c].container, (GtkWidget *) grid.buttons[r * acols + c].image);

                // Aggiunta componenti alla griglia
                gtk_grid_attach ((GtkGrid *) agrid, grid.buttons[r * acols + c].container, c, r, 1, 1);
                g_signal_connect (G_OBJECT (grid.buttons[r * acols + c].container), "button-press-event", ahandler, &grid.buttons[r * acols + c]);
            }
        }
    }

    // Imposta valori principali
    DBG_BLOCDESC ("imposto valori principali");
    grid.rows = arows;
    grid.cols = acols;
    grid.nummines = anummines;
    grid.game_status = GS_NORMAL;
    grid.selmines = 0;

    // Imposto valori caselle
    DBG_GENERIC ("imposto valori iniziali caselle");
    for (int r = 0; r < arows; r++) {
        for (int c = 0; c < acols; c++) {
            image_set_from_pix (grid.buttons[r * acols + c].image, PI_BUTTON_NORMAL);
            grid.buttons[r * acols + c].status = BS_NORMAL;
            grid.buttons[r * acols + c].content = BC_NEAR0;
            grid.buttons[r * acols + c].rown = r;
            grid.buttons[r * acols + c].coln = c;
        }
    }

    // Riempimento array con mine
    DBG_BLOCDESC ("riempimento array con mine e aggiornamento delle caselle vicine");
    int i = 0;
    while (i < anummines) {
        // Trova posizione casuale
        int c = rand () % acols;
        int r = rand () % arows;

        // Se cella non ancora assegnata, assegna e riempi le celle vicine
        if (grid.buttons[r * acols + c].content != BC_MINE) {
            DBG_GENERIC ("assegno mina e riempio caselle vicine a [" << c << "][" << r << "]");

            // Assegno mina
            grid.buttons[r * acols + c].content = BC_MINE;

            // Riempie le caselle vicine
            for (int rn = (r == 0 ? 0 : r - 1); rn <= (r == arows - 1 ? arows - 1 : r + 1); rn++) {
                for (int cn = (c == 0 ? 0 : c - 1); cn <= (c == acols - 1 ? acols - 1 : c + 1); cn++) {
                    switch (grid.buttons[rn * acols + cn].content) {
                        case BC_NEAR0: grid.buttons[rn * acols + cn].content = BC_NEAR1; break;
                        case BC_NEAR1: grid.buttons[rn * acols + cn].content = BC_NEAR2; break;
                        case BC_NEAR2: grid.buttons[rn * acols + cn].content = BC_NEAR3; break;
                        case BC_NEAR3: grid.buttons[rn * acols + cn].content = BC_NEAR4; break;
                        case BC_NEAR4: grid.buttons[rn * acols + cn].content = BC_NEAR5; break;
                        case BC_NEAR5: grid.buttons[rn * acols + cn].content = BC_NEAR6; break;
                        case BC_NEAR6: grid.buttons[rn * acols + cn].content = BC_NEAR7; break;
                        case BC_NEAR7: grid.buttons[rn * acols + cn].content = BC_NEAR8; break;
                        case BC_MINE: ; break;
                        default: assert (false);    // Non possibile
                    }
                }
            }

            i++;
        }
    }

    #ifdef DEBUG_MODE
        // Visualizza contenuto intera griglia
        DBG_GENERIC ("--- ARRAY_MINES ---");

        string v = "";

        for (int r = 0; r < arows; r++) {
            v = " |";
            for (int c = 0; c < acols; c++) {
                switch (grid.buttons[r * acols + c].content) {
                    case BC_NEAR0: v = v + " "; break;
                    case BC_NEAR1: v = v + "1"; break;
                    case BC_NEAR2: v = v + "2"; break;
                    case BC_NEAR3: v = v + "3"; break;
                    case BC_NEAR4: v = v + "4"; break;
                    case BC_NEAR5: v = v + "5"; break;
                    case BC_NEAR6: v = v + "6"; break;
                    case BC_NEAR7: v = v + "7"; break;
                    case BC_NEAR8: v = v + "8"; break;
                    case BC_MINE: v = v + "*"; break;
                    default: assert (false);    // Non possibile
                }
            }
            v = v + "|";
            DBG_GENERIC (v);
        }

        DBG_GENERIC ("");
    #endif

    return true;
}

/* -------------------------------------------------------------------------- */

/* Inizializza la griglia principale */
void grid_init () {
    DBG_FUNCNAME ("grid_init", "inizializzo la griglia principale");

    // Inizializza valori variabili
    DBG_BLOCDESC ("inizializzo valori variabili");
    grid.cols = 0;
    grid.rows = 0;
    grid.nummines = 0;
    grid.selmines = 0;
    grid.game_status = GS_NORMAL;
}

/* -------------------------------------------------------------------------- */

/* Partita persa, rivela le caselle delle mine */
void grid_lost_reveal () {
    DBG_FUNCNAME ("grid_lost_reveal", "partita persa. visualizza la posizione delle mine");

    grid.game_status = GS_LOST;    // Imposto partita persa

    // Ciclo per elaborare tutta la griglia
    for (int r = 0; r < grid.rows; r++) {
        for (int c = 0; c < grid.cols; c++) {
            // Controlla se c'è una mina ma non è stata segnalata
            if (((grid.buttons[r * grid.cols + c].status == BS_NORMAL) || (grid.buttons[r * grid.cols + c].status == BS_MAYBE)) && (grid.buttons[r * grid.cols + c].content == BC_MINE)) {
                DBG_GENERIC ("trovata mina inesplosa in " << c << "x" << r);
                image_set_from_pix (grid.buttons[r * grid.cols + c].image, PI_BUTTON_MINE);
            }

            // Controlla se la mina è stata segnalata in modo errato
            if ((grid.buttons[r * grid.cols + c].status == BS_CHECKED) && (grid.buttons[r * grid.cols + c].content != BC_MINE)) {
                DBG_GENERIC ("trovata mina sbagliata in " << c << "x" << r);
                image_set_from_pix (grid.buttons[r * grid.cols + c].image, PI_BUTTON_MINEERR);
            }
        }
    }
}

/* -------------------------------------------------------------------------- */

/* Partita vinta, rivela le caselle delle mine */
void grid_win_reveal () {
    DBG_FUNCNAME ("grid_win_reveal", "partita vinta. visualizza la posizione delle mine");

    grid.game_status = GS_WIN;    // Imposto partita vinta
    grid.selmines = grid.nummines;    // Sistemo numero mine segnalate

    // Ciclo per elaborare tutta la griglia
    for (int r = 0; r < grid.rows; r++) {
        for (int c = 0; c < grid.cols; c++) {
            // Controlla se c'è una mina
            if (grid.buttons[r * grid.cols + c].content == BC_MINE) {
                DBG_GENERIC ("trovata mina in " << c << "x" << r);
                image_set_from_pix (grid.buttons[r * grid.cols + c].image, PI_BUTTON_CHECKED);
            } else {
//                image_set_from_pix (grid.buttons[r * grid.cols + c].image, PI_BUTTON_PRESSED);
            }
        }
    }
}

/* -------------------------------------------------------------------------- */
